using System;
using System.EnterpriseServices;
using AopAlliance.Intercept;
using Sedna.Core.Event;
using log4net;
using NHibernate;
using Sedna.Core.Persistence.Hql;

namespace Sedna.Core.Persistence
{
	/// <summary>
	/// the main part of the Sedna Hibernate transactional 
	/// architecture. This class conforms the principles of the
	/// AOP Alliance in a way that it provides the 'around'.
	/// Its main function is to determine whether a call is transactional and requires a ISession 
	/// (i.e. either implements a <see cref="ISessionAware"/> interface or is marked with the <see cref="SessionalAttribute"/>)
	/// to be injected or not. In the latter case, an invocation proceeds further, while in the former case
	/// a current session is obtained from the <see cref="IPstSessionFactory"/> and is injected into 
	/// an invocation target (caller object instance). Furthermore, if a call is a transactional 
	/// (i.e. is marked with the <see cref="TransactionalAttribute"/>) either new transaction is created or 
	/// an invocation joins to a current transaction (by means of <see cref="ITxManager.BeginOrJoinTx"/> call)
	/// </summary>
	public class PstInterceptor : IMethodInterceptor
	{

		private ILog log = LogManager.GetLogger(typeof(PstInterceptor));

		private ITxManager txManager;
        //private IPstSessionFactory sessionFactory;
        private IEventHandlerRegistry eventHandlerRegistry;

        public ITxManager TxManager
		{
			set { txManager = value; }
		}

//	    public IPstSessionFactory SessionFactory
//	    {
//	        set { sessionFactory = value; }
//	    }

        public IEventHandlerRegistry EventHandlerRegistry
        {
            set { eventHandlerRegistry = value; }
        }
        
	    /// <summary>
		/// the sole mandatory public method that is called by the Spring AOP infrastructure
		/// when a corresponding method invocation is intercepted
		/// 
		/// <note>IMPORTANT: So far this method does not throw any exception and if an exception is thown by the underlying 
		/// invocation, this exception is written to the log, if a method was a transactional, a current transaction is
		/// rolledback and a NHibernate Session is closed. This might not be the best strategy for the exception handling
		/// and it has to be further elaborated.</note>
		/// </summary>
		/// <param name="invocation">an instance of AOP Alliance intercepted method invocation</param>
		/// <returns>this method must return a valid result of an invocation that is passed to the method</returns>
		public object Invoke(IMethodInvocation invocation)
		{            
			bool isTransactional = CheckTransactional(invocation);
            bool isSessional = CheckSessional(invocation);
            bool isEventful = CheckEventful(invocation);

            object result;

	        if (!isTransactional && !isSessional)
			{
                log.Debug(
                string.Format("Invoking method '{0}.{1}'",
                              invocation.Method.DeclaringType.ToString(), invocation.Method.Name));
			    
				result = invocation.Proceed();
			    
			    if (isEventful)
			        ProceedEvent(invocation, result);
			    
			    return result;
			}

  //          ISessionAware sessionAware = null;

            log.Debug(
                string.Format("[BEGIN] Invoking {0} method '{1}.{2}'",
                              isTransactional ? "Transactional" : "Sessional",
                              invocation.Method.DeclaringType.ToString(), invocation.Method.Name));
	        
            bool isNewSession = !NHibernateHelper.GetCurrentSession().IsOpen;

//            bool isNewSession = sessionFactory.BeginOrJoinHbmSession();

//            sessionAware = (ISessionAware)invocation.This;
	        
            log.Debug(String.Format("{0}. Method: {1}.{2}", isNewSession ? "Opening new session" : "Joining existent session",
                      invocation.Method.DeclaringType.ToString(), invocation.Method.Name));

		    
            bool isNewTx = false;
            if (isTransactional)
            {
                isNewTx = txManager.BeginOrJoinTx();
                log.Debug(String.Format("{0}. Method: {1}.{2}", isNewTx ? "Starting new transaction" : "Joining existent transaction",
                              invocation.Method.DeclaringType.ToString(), invocation.Method.Name));
            }

            try
            {
                log.Debug(String.Format("[Invocation BEGIN]: {0}.{1}", invocation.Method.DeclaringType,invocation.Method.Name));
                result = invocation.Proceed();
                log.Debug(String.Format("[Invocation END]: {0}.{1}", invocation.Method.DeclaringType, invocation.Method.Name));
                
                if (isTransactional && isNewTx)
                {
                    log.Debug(String.Format("Committing current transaction. Method: {0}.{1}", invocation.Method.DeclaringType, invocation.Method.Name));

                    txManager.CommitTx();
                }
                //Event generation
                if (isEventful)
                    ProceedEvent(invocation, result);
            }
            catch (Exception e)
            {
                log.Warn(String.Format("[Method]: {0}.{1}. Exception is thrown, rolling back a transaction and closing current session",
                             invocation.Method.DeclaringType, invocation.Method.Name), e);
                
                if (isTransactional && isNewTx)
                    txManager.RollbackTx();

                if ((isSessional || isTransactional)/* && !sessionFactory.GetSession().IsTransactionValid*/)
                {
                    //sessionFactory.CloseSessionIfAny();
                    NHibernateHelper.GetCurrentSession().Close();
                }
                
                throw PrepareException(e); 
            }
            finally
            {
                if ((isSessional || isTransactional) && 
                    (isNewSession && AllowToCloseNewSession(invocation)))
                {
                    //sessionFactory.CloseSessionIfAny();
                    NHibernateHelper.GetCurrentSession().Close();
                }
              
                log.Debug(
                    string.Format("[END] Invoking {0} method '{1}.{2}'",
                                  isTransactional ? "Transactional" : "Sessional",
                                  invocation.Method.DeclaringType.ToString(), invocation.Method.Name));
            }
	        
			return result;
		}


	    private bool AllowToCloseNewSession(IMethodInvocation invocation)
	    {
            Object[] attributes = invocation.StaticPart.GetCustomAttributes(typeof(SessionalAttribute), false);
	        
	        SessionalAttribute attribute = (attributes != null && attributes.Length > 0)
	                                           ? attributes[0] as SessionalAttribute
	                                           : null;
	        
            return attribute == null || attribute.CloseNewSession;
	    }

		private bool CheckTransactional(IMethodInvocation invocation)
		{
			Object[] attributes = invocation.StaticPart.GetCustomAttributes(typeof(TransactionalAttribute), false);
			return (attributes != null && attributes.Length != 0);
		}

		private bool CheckSessional(IMethodInvocation invocation)
		{
//			if (typeof(ISessionAware).IsAssignableFrom(invocation.StaticPart.DeclaringType))
//			{
//			    return true;
//			}
//		    else
//			{
                Object[] attributes = invocation.StaticPart.GetCustomAttributes(typeof(SessionalAttribute), false);
                return (attributes != null && attributes.Length != 0);
//			}
		}

        private bool CheckEventful(IMethodInvocation invocation)
        {
            Object[] attributes = invocation.StaticPart.GetCustomAttributes(typeof(EventfulAttribute), false);
            return (attributes != null && attributes.Length > 0);
        }

        private EventfulAttribute[] GetEventfulAttributes(IMethodInvocation invocation)
        {
            return (CheckEventful(invocation)
                        ? (EventfulAttribute[])invocation.StaticPart.GetCustomAttributes(typeof(EventfulAttribute), false)
                        : new EventfulAttribute[0]);
        }

        private void ProceedEvent(IMethodInvocation invocation, object result)
	    {
            //IList events = new ArrayList();
            foreach (EventfulAttribute eventfulAttribute in GetEventfulAttributes(invocation))
            {
                if (eventHandlerRegistry.CanHandle(eventfulAttribute.Name))
                {
                    #region Log Debug

                    if (log.IsDebugEnabled)
                    {
                        log.Debug(String.Format("Event {0} is handling", eventfulAttribute.Name));
                    }

                    #endregion

                    if (eventHandlerRegistry.HandleEvent(eventfulAttribute, invocation, result))
                    {
                        #region Log Debug

                        if (log.IsDebugEnabled)
                        {
                            log.Debug(String.Format("Event {0} handled", eventfulAttribute.Name));
                        }

                        #endregion
                    }
                    else
                    {
                        #region Log Debug

                        if (log.IsDebugEnabled)
                        {
                            log.Debug(String.Format("Event {0} was not handled", eventfulAttribute.Name));
                        }

                        #endregion
                    }

                }
                else
                {
                    #region Log Debug

                    if (log.IsDebugEnabled)
                    {
                        log.Debug(String.Format("Event {0} was not handled", eventfulAttribute.Name));
                    }

                    #endregion
                }

                #region Log Debug

                if (log.IsDebugEnabled)
                {
                    log.Debug(String.Format("Event {0} has been generated", eventfulAttribute.Name));
                }

                #endregion
            }
	    }
	    
	    private System.Exception PrepareException(System.Exception e)
	    {
            if (e is StaleObjectStateException)
            {
                return new SednaException(SednaExceptionCodes.ObjectIsModifiedByAnotherTx, e);
            }
            return new PstProxyException("Exception has been thrown during join point invocation.", e);
	    }

	}
}
